﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.UI.Core;
using Windows.UI.Xaml;

namespace CoachingOrganizer.WinPhone.UITools
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        #region private classes

        private class CommandCollection : Dictionary<string, CommandImpl>
        {
            public CommandCollection()
                : base()
            {

            }

            public CommandImpl GetCommand(string name, Action executeMethod, Func<bool> canExecuteMethod, bool useAutoRefresh)
            {
                if (ContainsKey(name))
                    return this[name];
                CommandImpl outResult = canExecuteMethod == null ? new CommandImpl(executeMethod, useAutoRefresh) : new CommandImpl(executeMethod, canExecuteMethod, useAutoRefresh);
                Add(name, outResult);
                return outResult;
            }

            public CommandImpl GetCommand(string name, Action executeMethod, Func<bool> canExecuteMethod)
            {
                return GetCommand(name, executeMethod, canExecuteMethod, true);
            }

            public CommandImpl GetCommand(string name, Action executeMethod, bool useAutoRefresh)
            {
                return GetCommand(name, executeMethod, null, useAutoRefresh);
            }

            public CommandImpl GetCommand(string name, Action executeMethod)
            {
                return GetCommand(name, executeMethod, null, true);
            }
        }

        #endregion

        private readonly SynchronizationContext _uiSynchronizationContext;
        private readonly CommandCollection _commands = new CommandCollection();
        private readonly Lazy<ICommand> _goBackCommand = new Lazy<ICommand>(() => new CommandImpl(() => UIHelper.GoBack(), () => UIHelper.CanGoBack(), true));
        private readonly Lazy<ICommand> _goForwardCommand = new Lazy<ICommand>(() => new CommandImpl(() => UIHelper.GoForward(), () => UIHelper.CanGoForward(), true));

        public ICommand GoBackCommand
        {
            get { return _goBackCommand.Value; }
        }

        public ICommand GoForwardCommand
        {
            get { return _goForwardCommand.Value; }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public ViewModelBase()
        {
            Window currentApplication = Window.Current;
            Debug.Assert(currentApplication != null, "Приложение Windows Phone не найдено");
            if (currentApplication != null)
            {
                if (!currentApplication.Dispatcher.HasThreadAccess)
                    throw new InvalidOperationException("Модель представления следует создавать из потока пользовательского интерфейса");
                _uiSynchronizationContext = SynchronizationContext.Current;
            }
        }

        /// <summary>
        /// вызвать в потоке пользовательского интерфейса
        /// </summary>
        /// <param name="action">вызываемый метод</param>
        protected void InvokeInUIThread(Action action)
        {
            if (_uiSynchronizationContext == SynchronizationContext.Current)
                action();
            else if (_uiSynchronizationContext != null)
                _uiSynchronizationContext.Send(o => action(), null);
        }

        /// <summary>
        /// вызвать в потоке пользовательского интерфейса
        /// </summary>
        /// <param name="action">вызываемый метод</param>
        /// <param name="state">передаваемый параметр</param>
        protected void InvokeInUIThread(Action<object> action, object state)
        {
            if (_uiSynchronizationContext == SynchronizationContext.Current)
                action(state);
            else if (_uiSynchronizationContext != null)
                _uiSynchronizationContext.Send(o => action(o), state);
        }

        /// <summary>
        /// асинхронно вызвать в потоке пользовательского интерфейса
        /// </summary>
        /// <param name="action">вызываемый метод</param>
        protected void BeginInvokeInUIThread(Action action)
        {
            if (_uiSynchronizationContext != null)
                _uiSynchronizationContext.Post(o => action(), null);
        }

        /// <summary>
        /// асинхронно вызвать в потоке пользовательского интерфейса
        /// </summary>
        /// <param name="action">вызываемый метод</param>
        /// <param name="state">передаваемый параметр</param>
        protected void BeginInvokeInUIThread(Action<object> action, object state)
        {
            if (_uiSynchronizationContext != null)
                _uiSynchronizationContext.Post(o => action(o), state);
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }

        protected void OnPropertyChanged<T>(Expression<Func<T>> propertyDelegate)
        {
            OnPropertyChanged(GetMemberName<T>(propertyDelegate));
        }

        protected void OnCommandCanExecuteChanged(string commandName)
        {
            CommandImpl command;
            if (_commands.TryGetValue(commandName, out command))
                command.NotifyCanExecuteChanged();
        }

        protected void OnCommandCanExecuteChanged<T>(Expression<Func<T>> propertyDelegate)
        {
            OnCommandCanExecuteChanged(GetMemberName<T>(propertyDelegate));
        }

        public CommandImpl GetCommand(string name, Action executeMethod, Func<bool> canExecuteMethod, bool useAutoRefresh)
        {
            return _commands.GetCommand(name, executeMethod, canExecuteMethod, useAutoRefresh);
        }

        public CommandImpl GetCommand(string name, Action executeMethod, Func<bool> canExecuteMethod)
        {
            return GetCommand(name, executeMethod, canExecuteMethod, true);
        }

        public CommandImpl GetCommand(string name, Action executeMethod, bool useAutoRefresh)
        {
            return GetCommand(name, executeMethod, () => true, useAutoRefresh);
        }

        public CommandImpl GetCommand(string name, Action executeMethod)
        {
            return GetCommand(name, executeMethod, () => true, true);
        }

        public CommandImpl GetCommand<T>(Expression<Func<T>> propertyDelegate, Action executeMethod, Func<bool> canExecuteMethod, bool useAutoRefresh)
        {
            return _commands.GetCommand(GetMemberName<T>(propertyDelegate), executeMethod, canExecuteMethod, useAutoRefresh);
        }

        public CommandImpl GetCommand<T>(Expression<Func<T>> propertyDelegate, Action executeMethod, Func<bool> canExecuteMethod)
        {
            return GetCommand(GetMemberName<T>(propertyDelegate), executeMethod, canExecuteMethod);
        }

        public CommandImpl GetCommand<T>(Expression<Func<T>> propertyDelegate, Action executeMethod, bool useAutoRefresh)
        {
            return GetCommand(GetMemberName<T>(propertyDelegate), executeMethod, useAutoRefresh);
        }

        public CommandImpl GetCommand<T>(Expression<Func<T>> propertyDelegate, Action executeMethod)
        {
            return GetCommand(GetMemberName<T>(propertyDelegate), executeMethod);
        }

        private static string GetMemberName<T>(Expression<Func<T>> propertyDelegate)
        {
            MemberExpression propertyDelegateExpression = propertyDelegate.Body as MemberExpression;
            if (propertyDelegateExpression == null)
                throw new InvalidOperationException("Передано не свойство");
            return propertyDelegateExpression.Member.Name;
        }
    }
}
